#include "dl_definitions.h"

#define out_link_data(x) (a_out_link_data[x-1])

static void TR_idle_ack(void);			// hook for NW layer to send ACKs

//prevents enabling app layer when not ready
static bool route_ready = false;


static OUT_LINK_DATA*	a_out_link_data;	//array of data for each outgoing link; size is c_out_links


static void DL_send_frame(FRAME* frame, size_t framelen, int linkno) {
	

	/*
	 * creating a timeout for EV_TIMER1 handler. Crutial part, since it determines when
	 * the next frame may enter the physical layer
	 */
	CnetTime   timeout;
	if(framelen == 0) return;
//	fprintf(stderr, "%s DL_send_frame(): bandwidth=%u\n", nodeinfo.nodename, linkinfo[linkno].bandwidth);
	timeout = framelen * (CnetTime)8000000 / linkinfo[linkno].bandwidth + 1; //intuition: 8 bits are a byte

	//saving and launching a timer, data indicates the corresponding link
	out_link_data(linkno).lasttimer = CNET_start_timer(EV_TIMER1, timeout, linkno);
	//writing frame to the physical layer
	CHECK(CNET_write_physical(linkno, (void *)frame, &framelen));
}



/*
 * SPLITTING BEGIN
 * ---------------------------------------------------------------------------------------------------------------------
 */

//transmits next piece of info
static void DL_transmit_next_info(int linkno) {
	//creating a frame for the upcoming transmission
	FRAME f;
	size_t framelen = FRAME_HEADER_SIZE;	
														//computing the overall frame length
	/*
	 * computation: ptr to the "buffer" for curr. processed pkt + offset
	 * offset = number of processed chunks times their size (based on mtu only)
	 */
	char* ptr_chunk =
	(char*)out_link_data(linkno).curr_info +
	(out_link_data(linkno).c_curr_part * out_link_data(linkno).s_max_chunk);
	
	if(out_link_data(linkno).c_curr_part == out_link_data(linkno).c_total_parts - 1) {			//last chunk reached, maybe size != mtu
		
		memcpy(&(f.info), ptr_chunk, out_link_data(linkno).s_last_chunk);
		f.last_segment = out_link_data(linkno).seq + 100;										//this is the last segment
		out_link_data(linkno).c_curr_part = 0;
		out_link_data(linkno).f_pkt_finished = 1;												//link free again
		framelen += out_link_data(linkno).s_last_chunk;						
		
	} else {																					//not the last chunk, size = mtu
		
		memcpy(&(f.info), ptr_chunk, out_link_data(linkno).s_max_chunk);						//copying next part of the packet into the frame
		
		f.last_segment = out_link_data(linkno).seq;
		out_link_data(linkno).c_curr_part++;
		framelen += out_link_data(linkno).s_max_chunk;
		
	}
	DL_send_frame(&f, framelen, linkno);			
	
}


//handles the packet splitting
static void DL_process_packet (PACKET *pkt, int linkno)
{
	out_link_data(linkno).infolen = PACKET_SIZE_PTR(pkt);
	
	out_link_data(linkno).curr_info = (INFO*) pkt;												//saving ptr to curr. processed packet
	
	out_link_data(linkno).s_max_chunk = linkinfo[linkno].mtu - FRAME_HEADER_SIZE;				//max. size of an info chunk depends on mtu and frame header size

	if(out_link_data(linkno).infolen == 0) return;
	out_link_data(linkno).c_total_parts = 
		out_link_data(linkno).infolen / out_link_data(linkno).s_max_chunk + 1;					//+1 because we need ceil rounding
	
	out_link_data(linkno).s_last_chunk =														//size of last chunk = pkt size - number of fully loaded chunks
		out_link_data(linkno).infolen - 
			out_link_data(linkno).s_max_chunk * (out_link_data(linkno).c_total_parts - 1);			

	out_link_data(linkno).f_pkt_finished = 0;													//starting packet transmission
	out_link_data(linkno).f_link_ready = 0;
	out_link_data(linkno).seq++;
	
	//storing 100 for last frame flag, 0-99 for sequencing
	if (out_link_data(linkno).seq > 99) {
		out_link_data(linkno).seq = 0;
	}
	
	DL_transmit_next_info(linkno);																//transmitting next chunk of data	
}
/*
 * ---------------------------------------------------------------------------------------------------------------------
 * SPLITTING END
 */





/*
 * BUFFERING BEGIN
 * ---------------------------------------------------------------------------------------------------------------------
 */
static void DL_pull_packet(int linkno)
{	
	if (out_link_data(linkno).f_link_ready && queue_nitems(out_link_data(linkno).buffer) > 0) {		//buffer not empty AND and link ready; 	
		size_t useless;		//size of the packet, currently not used
		PACKET* pkt;
		pkt = queue_remove(out_link_data(linkno).buffer, &useless);
		DL_process_packet(pkt, linkno);		//split and send packet
	} else
		if (out_link_data(linkno).f_link_ready && queue_nitems(out_link_data(linkno).buffer) == 0)
			TR_idle_ack();
}	


static void DL_buffer_packet(PACKET* pkt, int linkno)
{	
	
	if(queue_nitems(out_link_data(linkno).buffer) >= out_link_data(linkno).S_BUFFER) {	//buffer full, disable msg generation
		
		//empty a little bit
		for(int i = 0; i < queue_nitems(out_link_data(linkno).buffer) /2; i++){
			size_t useless;		//size of the packet, currently not used
			PACKET* pkt;
			pkt = queue_remove(out_link_data(linkno).buffer, &useless);
			free(pkt);
		}
		//thats not a disc packet, drop it!	
		//if(pkt->src != pkt->dest)return;
		
		//disc packets have priority
		//if(queue_nitems(out_link_data(linkno).buffer) >= out_link_data(linkno).S_BUFFER + 5) return;
		
	}
	
	queue_add(out_link_data(linkno).buffer, pkt, sizeof(PACKET));
	
	if(queue_nitems(out_link_data(linkno).buffer) == 1)									//buffer was empty before (added one above)
	{   		
        DL_pull_packet(linkno);															//call to get first packet 
	}
}		

/*
 * ---------------------------------------------------------------------------------------------------------------------
 * BUFFERING END
 */


//timeout handling: link became free
static void DL_timeouts (CnetEvent ev, CnetTimerID timer, CnetData data) {
	
	
	int used_link = data; 								//determine via timer_data

	if (out_link_data(used_link).f_pkt_finished == 0) {	//if current packet still not done
//		fprintf(stderr, "%s DL_timeouts(): pre transmit_next_info()\n", nodeinfo.nodename);
		DL_transmit_next_info(used_link);				//transmit its next part
	} else {
		out_link_data(used_link).f_link_ready = 1;		//pkt done and enough time passed
//		fprintf(stderr, "%s DL_timeouts(): pre free()\n", nodeinfo.nodename);
		free(out_link_data(used_link).curr_info);		//cleaning the space allocated for the packet (on nw layer)
//		fprintf(stderr, "%s DL_timeouts(): pre pull_packet()\n", nodeinfo.nodename);
		DL_pull_packet(used_link);						//else begin with new packet
	}
}


